# -*- coding: utf-8 -*-
# standard
import os
import sys
import re
import threading
import time
import ctypes
from datetime import timezone
from dataclasses import dataclass
from queue import Queue
from queue import Empty

# third
import requests
from pydantic import validate_call
from cachelib import SimpleCache
from sqlalchemy.schema import UniqueConstraint 

# local
_P_PATH =  os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if _P_PATH not in sys.path:
    sys.path.append(_P_PATH)
from models.base_model import *
from models.huey_tasks import *


"""
@Title:   物联网相关模块
@File: iot_model.py
@Author: walle 2024年10日07日 03时27分44秒
@Version: 1.0.0
@Desc: 
"""

LOCAL_TZ = timezone(timedelta(hours=8))
MEM_CACHE = SimpleCache()
MEM_CACHE.clear()
THIRD_PARTY_SENSORS_CACHE_KEY = "third-party-sensors"  # 第三方传感器缓存key
ALERT_COND_CONF_KEY = "alert-condition-conf"  # 报警配置的缓存key
THRESHOLD_CACHE_KEY = "threshold-alert"  # 阈值警告的缓存key 
OFFLINE_CACHE_KEY = "offline-alert"  # 离线预警的缓存的key
ALERT_COND_CONF_CACHE_TTL = 1800  # 报警配置的缓存时间
# THRESHOLD_CACHE_TTL = 86400  # 阈值警告的缓存时间 
# OFFLINE_CACHE_TTL = 86400  # 离线预警的缓存的key


DATA_AUTHORIZATION = 'Bearer 4f6160748cca786e56898ad945589d92'  # 接受数据的 authorization
ALERT_AUTHORIZATION = 'Bearer 08b5a2a9692d77a7f6e79b4fb212521f'  # 接受influxdb警告的 authorization
GET_AUTHORIZATION = 'Bearer 85b5a2s5479dr3n4f6t31b4fb541739d'  # 接受获取请求用的 authorization
SETTING_AUTHORIZATION = 'Bearer b04ca1938e393a8ce55a85eca3a3befb'  # 接受设置请求用的 authorization
INSTRUCTION_AUTHORIZATION = 'Bearer d710f8d0d124a914fda08378c1102879'  # 接受指令请求用的 authorization


class EventPriority(Enum):
    """
    事件优先级
    """
    WARNING: str = "预警"
    ALERT: str = "告警"
    
    @classmethod
    def get_instance(cls, trigger_name: str) -> "EventPriority":
        if trigger_name in TRIGGER_PRIORITY_MAP:
            return TRIGGER_PRIORITY_MAP[trigger_name]
        else:
            return EventPriority.WARNING
        
    @classmethod
    def is_alert(cls, trigger_name: str) -> int:
        """
        判断一个触发器时间是告警还是预警
        :return: _description_ True 表示告警，False表示预警
        """
        priority = cls.get_instance(trigger_name)
        return 1 if priority == EventPriority.ALERT else 0
    
    @classmethod
    def repair_all_zabbix_alerts_priority(cls, days: int = 3) -> None:
        """
        修正zabbix 触发器的优先级为我们自定义的优先级
        :param severity: _description_
        :return: _description_
        """
        db_session = new_db_session(sync=True)
        s = select(ZabbixAlertOrm).where(ZabbixAlertOrm.event_time >= datetime.now() - timedelta(days=days))
        results = db_session.scalars(s).all()
        all_num = len(results)
        updated_num = 0
        for r in results:
            if not r.priority:
                r.priority = cls.get_instance(r.trigger_name).value
                updated_num += 1
        db_session.commit()
        db_session.close()
        logger.debug(f"查询到 {all_num} 条记录，修正了 {updated_num} 条记录的优先级")


class CacheType(Enum):
    """
    缓存类型
    """
    ALERT_CONF: str = "报警配置"
    THRESHOLD_ALERT: str = "阈值报警"
    OFFLINE_ALERT: str = "离线报警"


def generate_customer_bucket_name(customer_id: int) -> str:
    """
    生成客户bucket名称
    """
    return f"customer_{customer_id}"


def generate_sensor_base_key(uuid: str, group_id: int, sensor_sn: str) -> str:
    """
    生成传感器的基础key
    """
    return f"{uuid}-{group_id}-{sensor_sn}"


def convert_influxdb_datetime(influx_time: str) -> datetime:
    """
    将 influxdb 的时间字符串 转换为 datetime
    注意，由于对应的数据库的字段是无时区的，会导致再读取的时候失去时区信息而回复原来的无时区的字面值，
    所以本函数虽然更符合逻辑，但是并不适用本案例。请使用 convert_datetime_utc_time_str_to_local
    influxdb 的时间字符串格式为 2023-10-11T15:00:00Z, 而且是 UTC 时间
    :param datetime: _description_
    :return: _description_
    """
    t1s = influx_time.replace('Z', '')
    t1 = datetime.strptime(t1s, '%Y-%m-%dT%H:%M:%S')
    t1 = t1.replace(tzinfo=timezone.utc)
    t2 = datetime.fromtimestamp(t1.timestamp(), tz=LOCAL_TZ)
    return t2 


def convert_datetime_utc_time_str_to_local(influx_time: str) -> datetime:
    """
    使用直接加时区的方法将 influxdb 的时间字符串 转换为 datetime
    这是因为本案例存储时间对应的数据库的字段是无时区的，所以智能改时间的变字面值
    """
    t1s = influx_time.replace('Z', '')
    t1 = datetime.strptime(t1s, '%Y-%m-%dT%H:%M:%S')
    t2 = t1 + timedelta(hours=8)
    return t2


class SensorCategory(MyEnum):
    """
    传感器类型
    """
    TEMPERATURE: str = "温度传感器"
    ELECTRICITY: str = "电量传感器"

    @classmethod
    def get_influxdb_measurement(cls, sensor_category: str) -> str:
        """
        获取 influxdb 的 measurement
        """
        if sensor_category == cls.TEMPERATURE.value:
            return "temperature"
        elif sensor_category == cls.ELECTRICITY.value:
            return "electricity"
        else:
            raise ValueError("Invalid sensor_category")

class SensorEventType(MyEnum):
    """
    温度传感器事件类型
    这个模型和 iot-collector 共用
    """
    THRESHOLD: str = "超过阈值"
    OFFLINE: str = "设备离线"


class AlertStatus(MyEnum):
    # 定义一个枚举类，表示警告的状态
    OK: int = 0  # 定义一个常量，表示警告状态为已恢复
    ERR: int = 1 # 定义一个常量，表示警告状态为错误


class ElectricitySensorEventType(MyEnum):
    """
    电量传感器事件类型
    这个模型和 iot-collector 共用
    """
    OVER_CURRENT: str = "过流"
    UNDER_CURRENT: str = "欠流"
    OVER_VOLTAGE: str = "过压"
    UNDER_VOLTAGE: str = "欠压"
    OVER_POWER: str = "过载"
    HEIGHT_TEMPERATURE: str = "高温"
    OFFLINE: str = "设备离线"


class InfluxDBAlertLevel(MyEnum):
    """
    InfluxDB 发送的警告的级别
    """
    CRIT: str = "crit"
    INFO: str = "info"
    WARN: str = "warn"
    OK: str = "ok"


class AddTemperatureSensorParams(BaseModel):
    """
    添加温度传感器参数
    部分， 全表在 aiops 侧
    """
    sensor_category: str = Field(default=SensorCategory.TEMPERATURE.value, description="传感器类型")
    merchant_no: str | None = Field(None, description="商户iD 仅限第三方平台")
    uuid: str = Field(..., description="主机的设备UUID 用于关联和查询")
    group_id: int = Field(..., description="组ID 用于查询")
    sensor_sn: str = Field(..., description="传感器ID 用于查询")
    sensor_name: str | None = Field(None, description="传感器名称 冗余字段")
    mode_type: str | None = Field(None, description="传感器型号 冗余字段")
    manufacturer: str = Field(..., description="传感器厂商 必须 适用对应的接口获取选项")
    alias: str = Field(..., description="传感器别名 必须")
    desc: str | None = Field(None, description="传感器描述")
    picture_id: int | None = Field(None, description="图片id")
    customer_id: int = Field(..., description="品牌用户id")
    customer_name: str = Field(..., description="品牌名称")
    store_id: int = Field(..., description="门店id")
    store_name: str = Field(..., description="门店名称")
    threshold_alert: int = Field(1, description="是否开启阈值报警 0-不开启 1-开启")
    offline_alert: int = Field(1, description="是否开启离线报警 0-不开启 1-开启")
    min_threshold: float | None = Field(None, description="最小报警阈值, 低于此值可能触发警告")
    max_threshold: float | None = Field(None, description="最大报警阈值, 高于此值可能触发警告")
    unit: str = Field("℃", description="单位 默认摄氏度")


class EditTemperatureSensorParams(AddTemperatureSensorParams):
    """
    编辑温度传感器参数
    """
    id: int = Field(..., description="主键")
    sensor_category: str = Field(default=SensorCategory.TEMPERATURE.value, description="传感器类型")
    merchant_no: str | None = Field(None, description="商户iD 仅限第三方平台")
    operator_customer_id: int | None = Field(None, description="操作者关联的品牌id")
    uuid: str | None = Field(None, description="主机的设备UUID 用于关联和查询")
    customer_id: int | None = Field(None, description="品牌用户id")
    customer_name: str | None = Field(None, description="品牌名称")
    store_id: int | None = Field(None, description="门店id")
    store_name: str | None = Field(None, description="门店名称")
    group_id: int | None = Field(None, description="组ID 用于查询")
    sensor_sn: str | None = Field(None, description="传感器ID 用于查询")
    sensor_name: str | None = Field(None, description="传感器名称 冗余字段")
    mode_type: str | None = Field(None, description="传感器型号 冗余字段")
    manufacturer: str | None = Field(None, description="传感器厂商")
    alias: str | None = Field(None, description="传感器别名 必须")
    desc: str | None = Field(None, description="传感器描述")
    picture_id: int | None = Field(None, description="图片id")
    threshold_alert: int | None = Field(None, description="是否开启阈值报警 0-不开启 1-开启")
    offline_alert: int | None = Field(None, description="是否开启离线报警 0-不开启 1-开启")
    min_threshold: float | None = Field(None, description="最小报警阈值, 低于此值可能触发警告")
    max_threshold: float | None = Field(None, description="最大报警阈值, 高于此值可能触发警告")
    unit: str | None = Field(None, description="单位  默认摄氏度(℃)")


class CommonDeleteParams(BaseModel):
    """
    通用删除参数
    """
    ids: List[int] = Field(..., description="id的数组")


class AlertSetting(BaseModel):
    """报警设置"""
    is_existed: int = Field(1, description="当前传感器对应的配置是否存在")
    sensor_category: str = Field(..., description='传感器类型')
    threshold_alert: int = Field(0, description="是否开启阈值报警 0-不开启 1-开启")
    offline_alert: int = Field(0, description="是否开启离线报警 0-不开启 1-开启")

class TemperatureSensorAlertSetting(AlertSetting):
    """
    温度传感器报警设置
    """ 
    min_threshold: float | None = Field(None, description="最小报警阈值, 低于此值可能触发警告")
    max_threshold: float | None = Field(None, description="最大报警阈值, 高于此值可能触发警告")


def generate_conf_cache_key(uuid: str, group_id: int, sensor_sn: str) -> str:
    # 生成配置缓存的key
    return f"{ALERT_COND_CONF_KEY}_{uuid}_{group_id}_{sensor_sn}"


def reload_sensor_conf_cache(sensor: "TemperatureSensorOrm") -> Resp:
    """
    重新加载 配置缓存
    """
    key = generate_conf_cache_key(uuid=sensor.uuid, group_id=sensor.group_id, sensor_sn=sensor.sensor_sn)
    conf = sensor.get_alert_setting()
    MEM_CACHE.set(key=key, value=conf, timeout=ALERT_COND_CONF_CACHE_TTL)


def remove_sensor_conf_cache(sensor: "TemperatureSensorOrm") -> Resp:
    """
    移除 配置缓存
    """
    key = generate_conf_cache_key(uuid=sensor.uuid, group_id=sensor.group_id, sensor_sn=sensor.sensor_sn)
    MEM_CACHE.delete(key=key)
    

def get_alert_condition_from_cache(sensor_category: str, uuid: str, group_id: int, 
                                   sensor_sn: str, db_session: Session = None) -> AlertSetting:
    """
    从缓存读取报警配置
    有返回一个 AlertSetting 对象，其中 is_existed == 1
    没有配置返回 一个 AlertSetting 对象，其中 is_existed == 0
    出错返回None, 这种状态是不正常的，

    """
    key = generate_conf_cache_key(uuid=uuid, group_id=group_id, sensor_sn=sensor_sn)
    conf = MEM_CACHE.get(key)
    if conf is None:
        db_session, need_close = (new_db_session(), True) if db_session is None else (db_session, False)
        s = select(TemperatureSensorOrm).where(and_(
            TemperatureSensorOrm.uuid == uuid, 
            TemperatureSensorOrm.group_id == group_id,
            TemperatureSensorOrm.sensor_sn == sensor_sn
        ))
        sensor = db_session.scalar(s)
        if sensor is None:
            cls = TemperatureSensorAlertSetting if sensor_category == SensorCategory.TEMPERATURE.value else ElectricitySensorAlertSetting
            conf = cls(is_existed=0, sensor_category=sensor_category)
        else:
            conf = sensor.get_alert_setting()
            MEM_CACHE.set(key, conf, timeout=ALERT_COND_CONF_CACHE_TTL)
        if need_close:
            db_session.close()
    return conf



class TemperatureSensorManufacturer(MyEnum):
    """
    温度传感器生产商
    """
    TIAN_QING: str = "上海天勍"
    WEI_EN: str = "沈阳维恩"


class ElectricitySensorManufacturer(MyEnum):
    """
    电力传感器生产商
    """
    TIAN_QING: str = "上海天勍"
    RUI_JU: str = "上海睿据"


class SensorStatus(MyEnum):
    """
    传感器状态
    """
    OK: int = 0  # 正常
    OFFLINE: int = 1  # 离线
    OVER_THRESHOLD: int = 2  # 超过阈值


class SensorOrm(Base, OrmTools):
    """
    传感器
    """
    __tablename__ = 'sensor'
    __table_args__ = (
        UniqueConstraint('uuid', 'group_id', 'sensor_sn'),  # 联合主键
        {'comment': '传感器基类'}
    )
    id = Column(Integer, primary_key=True)
    sensor_category = Column(String(64), nullable=False, comment='传感器类型 鉴别器')
    merchant_no = Column(String(64), nullable=True, comment="商户ID,仅限第三方平台传感器")
    uuid = Column(String(64), nullable=False, comment='主机的设备UUID 或者host_id 冗余字段 用于关联和查询')
    group_id = Column(Integer, nullable=True, default=1, comment='组ID  没有组id默认1， 用于查询')
    sensor_sn = Column(String(64), nullable=False, comment='传感器ID 用于查询')
    sensor_name = Column(String(64), nullable=True, default="", comment='传感器名称 冗余字段')
    mode_type = Column(String(64), nullable=True, default="", comment='传感器型号 冗余字段')
    manufacturer = Column(String(64), nullable=True, default=None, comment='传感器生产商')
    alias = Column(String(64), nullable=False, comment='传感器别名 必须')
    desc = Column(String(256), nullable=True, default=None, comment='设备描述')
    picture_id = Column(Integer, nullable=True, default=None, comment='设备图片ID')
    customer_id = Column(Integer, nullable=True, default=None, comment='品牌客户ID')
    customer_name = Column(String(255), nullable=True, default=None, comment='品牌客户名称')
    store_id = Column(Integer, nullable=True, default=None, comment='门店ID')
    store_name = Column(String(128), nullable=True, default=None, comment='门店名称')
    threshold_alert = Column(Integer, default=1, comment='是否开启阈值报警 0-不开启 1-开启')
    offline_alert = Column(Integer, default=1, comment='是否开启离线报警 0-不开启 1-开启')
    is_over_threshold = Column(Integer, default=AlertStatus.OK.value, comment='是否超过阈值 0-未超过 1-超过')
    is_offline = Column(Integer, default=AlertStatus.OK.value, comment='是否离线 0-在线 1-离线  由于电力传感器目前不完善，因此电力传感器只有 is_offline的报警')
    is_deleted = Column(Integer, nullable=True, default=0, comment="是否删除 0-未删除 1-已删除")
    create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间')
    update_time = Column(DateTime, nullable=True, default=datetime.now, onupdate=datetime.now, comment='最后修改时间')

    __mapper_args__ = {
        'polymorphic_identity':'sensor',
        'polymorphic_on':sensor_category
    }

    @classmethod
    def get_store_sensor_ids(cls, customer_id: int, store_id: int, 
                             sensor_category: str, db_session: Session) -> List[int]:
        """
        获取门店的所有传感器的id
        """
        s = select(cls.id).where(and_(
            cls.customer_id == customer_id, 
            cls.store_id == store_id,
            cls.sensor_category == sensor_category
            ))
        sensor_ids = db_session.scalars(s).all()
        return sensor_ids
    
    @staticmethod
    def load_push_tasks():
        """
        加载推送任务
        """
        # 查询所有未推送的消息
        db_session = new_db_session()
        s = select(MessagePushRecordOrm).where(MessagePushRecordOrm.push_result == None)
        records = db_session.scalars(s).all()
        for record in records:
            # 发送消息到任务中心
            params = record.get_task_instance()
            push_task_queue(params)
        db_session.close()
        logger.debug(f"从数据库加载{len(records)}个推送任务")

    def add_notification_task(self, message: str, alert_time: datetime, 
                              old_status: int, new_status: int,
                              type: str):
        """
        添加一条通知任务
        :param message: _description_
        :param alert_time: _description_
        :param new_status: _description_
        :param raw_event_id: _description_
        """
        kw = {
            "customer_id": self.customer_id,
            "customer_name": self.customer_name,
            "store_id": self.store_id,
            "store_name": self.store_name,
            "sensor_id": self.id,
            "sensor_name": self.alias,
            "prev_status": old_status,
            "sensor_status": new_status,
            "type": type,
            "sensor_category": self.sensor_category,
            "message": message,
            "alert_time": alert_time,
            "raw_obj_id": self.id,
        }
        params = AddMessagePushRecordParams(**kw)
        result = push_task_queue(params)
        logger.debug(f"添加推送任务{result}")

    def update_status_and_send_message(self, old_status: int, new_status: int,
                                       type: str):
        """
        更新传感器状态并发送消息
        :param alert_time: _description_
        :param raw_obj_id: _description_
        """
        if type == SensorEventType.THRESHOLD.value:  # 这是阈值变换的情况
            self.is_over_threshold = new_status
            if new_status == 1:  # 超过阈值
                message = "测量值异常"
            else:
                message = "测量值恢复正常"
        else:  # 这是离线状态变换的情况
            self.is_offline = new_status
            if new_status == 1:  # 离线
                message = "处于离线状态"
            else:
                message = "恢复在线状态"
        
        # 发送消息到任务中心
        self.add_notification_task(message=message,
                                   old_status=old_status,
                                   new_status=new_status,
                                   type=type,
                                   alert_time=datetime.now())


class TemperatureSensorOrm(SensorOrm):
    """
    温度传感器
    """
    __tablename__ = 'temperature_sensor'
    __table_args__ = (
        {'comment': '温度传感器'}
    )
    id = Column(ForeignKey("sensor.id", ondelete='DELETE'), primary_key=True)
    min_threshold = Column(Float, nullable=True, default=None,  comment='最小报警阈值, 高于此值可能触发警告')
    max_threshold = Column(Float, nullable=True, default=None,  comment='最大报警阈值, 高于此值可能触发警告')
    unit = Column(String(64), nullable=True, default="℃", comment='单位')

    __mapper_args__ = {
        'polymorphic_identity': SensorCategory.TEMPERATURE.value,
    }
    
    @classmethod
    def add(cls, params: AddTemperatureSensorParams) -> Resp:
        db_session, resp = new_db_session(), Resp()
        s = select(SensorOrm).where(and_(
            SensorOrm.uuid == params.uuid,
            SensorOrm.group_id == params.group_id,
            SensorOrm.sensor_sn == params.sensor_sn,
        ))
        one = db_session.scalar(s)
        if one:
            resp.message = '设备已存在'
        else:
            kw = params.model_dump()
            obj = cls(**kw)
            db_session.add(obj)
            db_session.commit()
            db_session.refresh(obj)
            reload_sensor_conf_cache(obj)  # 重新加载配置缓存
            resp.data = {
                "store_id": obj.store_id,
                "current_sensor_id": obj.id,
                "sensor_category": obj.sensor_category,
                "sensor_ids": cls.get_store_sensor_ids(customer_id=params.customer_id,
                                                       store_id=params.store_id,
                                                       sensor_category=obj.sensor_category,
                                                       db_session=db_session)
            }
        db_session.close()
        return resp
    
    @classmethod
    def edit(cls, params: EditTemperatureSensorParams) -> Resp:
        db_session, resp = new_db_session(), Resp()
        one = db_session.get(cls, params.id)
        if not one:
            resp.message = f"传感器不存在: ID = {params.id}"
        elif params.operator_customer_id and one.customer_id != params.operator_customer_id:
            resp.message = "不能操作其他公司的传感器"
        elif db_session.scalar(select(func.count(cls.id)).where(and_(
            cls.uuid == params.uuid,
            cls.group_id == params.group_id,
            cls.sensor_sn == params.sensor_sn,
            cls.id != params.id
        ))) > 0:
            resp.message = '设备已存在'
        else:
            kw = params.model_dump(exclude=['id', 'operator_customer_id'], exclude_none=True)
            for k, v in kw.items():
                if hasattr(one, k):
                    setattr(one, k, v)
            db_session.commit()
            db_session.refresh(one)
            reload_sensor_conf_cache(one)  # 重新加载配置缓存
            resp.data = {
                "store_id": one.store_id,
                "current_sensor_id": one.id,
                "sensor_category": one.sensor_category,
                "sensor_ids": cls.get_store_sensor_ids(customer_id=one.customer_id,
                                                       sensor_category=one.sensor_category,
                                                            store_id=one.store_id,
                                                            db_session=db_session)
            }
        db_session.close()
        return resp
    
    @classmethod
    def delete(cls, ids: List[int]) -> Resp:
        db_session, resp = new_db_session(), Resp()
        if not ids:
            resp.message = "缺少参数"
        else:
            customer_id, sensor_category, store_id = None, None, None
            picture_ids = set()
            for id in ids:
                sensor = db_session.get(cls, id)
                if sensor is None:
                    resp.message = f"无效的传感器id: {id}"
                    break
                if sensor.picture_id:
                    picture_ids.add(sensor.picture_id)
                if customer_id is None:
                    customer_id = sensor.customer_id
                    sensor_category = sensor.sensor_category
                    store_id = sensor.store_id
                else:
                    if store_id != sensor.store_id:
                        resp.message = "只能删除同一个门店的传感器"
                        break
                remove_sensor_conf_cache(sensor)  # 移除配置缓存
            if resp:
                s = delete(cls).where(cls.id.in_(ids))
                s0 = delete(SensorOrm).where(SensorOrm.id.in_(ids))
                result = db_session.execute(s)
                db_session.execute(s0)
                db_session.commit()
                logger.warning(f"删除结果: {result}")
                resp.data = {
                    "store_id": store_id,
                    "current_sensor_id": ids,
                    "sensor_category": sensor_category,
                    "picture_ids": list(picture_ids), # 需要删除的图片id
                    "sensor_ids": cls.get_store_sensor_ids(customer_id=customer_id,
                                                           sensor_category=sensor_category,
                                                                store_id=store_id,
                                                                db_session=db_session)
                }
            else:
                pass      
        db_session.close()
        return resp

    def get_alert_setting(self) -> TemperatureSensorAlertSetting:
        return TemperatureSensorAlertSetting(sensor_category=self.sensor_category,
                                             threshold_alert=self.threshold_alert,
                                             offline_alert=self.offline_alert,
                                             min_threshold=self.min_threshold,
                                             max_threshold=self.max_threshold,
                                             )
    

class TemperatureSensorEventOrm(Base, OrmTools):
    """
    温度传感器事件记录
    这里保存的是原始记录，可以在调试 influxdb 的alert 作为参考
    :param Base: _description_
    :param OrmTools: _description_
    """
    __tablename__ = 'temperature_sensor_event'
    __table_args__ = {'comment': '温度传感器报警记录'}
    id = Column(Integer, primary_key=True, autoincrement=True, comment='主键')
    type = Column(String(31), nullable=False, comment='事件类型 SensorEventType ElectricitySensorEventType')
    check_name = Column(String(128), nullable=False, comment='检查项名称')
    level = Column(String(32), nullable=False, comment='报警级别')
    endpoint_name = Column(String(128), nullable=False, comment='接收事件的断点名称')
    rule_name = Column(String(128), nullable=False, comment='匹配事件规则名称')
    uuid = Column(String(64), nullable=False, comment='主机的设备UUID 用于关联和查询')
    group_id = Column(Integer, nullable=False, comment='组ID 用于查询')
    sensor_sn = Column(String(64), nullable=False, comment='传感器ID 用于查询')
    message = Column(String(256), nullable=False, comment='报警信息')
    start = Column(DateTime, nullable=False, comment='报警开始时间')
    time = Column(DateTime, nullable=False, comment='报警时间')
    stop = Column(DateTime, nullable=False, comment='报警结束时间')
    value = Column(Float, nullable=False, comment='值')
    customer_id = Column(Integer, nullable=False, comment="品牌客户id")
    store_id = Column(Integer, nullable=False, comment="门店id")

    @staticmethod
    def convert_param(**kwargs) -> dict:
        """
        转换参数
        """
        start = kwargs.get('_start', None)
        if start:
            start = convert_datetime_utc_time_str_to_local(start)
        time = kwargs.get('_time', None)
        if time:
            time = convert_datetime_utc_time_str_to_local(time)
        stop = kwargs.get('_stop', None)
        if stop:
            stop = convert_datetime_utc_time_str_to_local(stop)
        kw = {
            'check_name': kwargs.get('_check_name', ''),
            'level': kwargs.get('_level', ""),
            'endpoint_name': kwargs.get('_notification_endpoint_name', ''),
            'rule_name': kwargs.get('_notification_rule_name', ''),
            'uuid': kwargs.get('uuid', ''),
            'group_id': kwargs.get('grp', ''),
            'sensor_sn': kwargs.get('id', ''),
            'message': kwargs.get('_message', ''),
            'type': kwargs.get('type', ''),
            'start': start,
            'time': time,
            'stop': stop,
            'customer_id': kwargs.get('customer_id'),
            'store_id': kwargs.get('store_id'),
            'value': kwargs.get('t', 0.0),
        }
        return kw

    @classmethod
    def add_one(cls, **kwargs):
        """
        添加一条 event  同时同步 InFluxDBAlertOrm
        注意，阈值的事件可能因为自定义阈值上下限造成阈值对应的 alert 被恢复
        """
        kw = cls.convert_param(**kwargs)
        db_session = new_db_session()
        conf = get_alert_condition_from_cache(uuid=kw['uuid'], group_id=kw['group_id'],
                                              sensor_category=SensorCategory.TEMPERATURE.value,
                                              sensor_sn=kw['sensor_sn'], db_session=db_session)
        if conf.is_existed == 0:  # 没有配置的传感器不记录alert
            db_session.close()
            return
        status = "ok"
        try:
            event = cls(**kw)  # 把influxdb发来的事件写入数据库
            db_session.add(event)
            db_session.commit()
            db_session.refresh(event)
        except Exception as e:
            logger.error(e)
            db_session.rollback()
            status = "err"
        finally:
            if status == "ok":  # 之前代码执行没有出错
                # 根据配置判断是否满足报警条件
                if event.type == SensorEventType.THRESHOLD.value:  # 这是一个阈值警报
                    if conf.threshold_alert:  # 开启了报警
                        InFluxDBAlertOrm.add_or_update_temperature_threshold_alert(event=event, conf=conf, db_session=db_session)  
                elif event.type == SensorEventType.OFFLINE.value:  # 这是一个阈值
                    if conf.offline_alert:  # 开启了报警
                        InFluxDBAlertOrm.add_or_update_temperature_offline_alert(event=event, db_session=db_session) 
                else:
                    pass
            else:
                pass
            db_session.close()


class InFluxDBAlertOrm(Base, OrmTools):
    """
    从influxdb发送过来的事件中提取的警告信息, 可以直接应用于业务
    本模型的功能是写入alert,aiops后台有一个同样的模型用于查询.
    """
    __tablename__ = "influxdb_alert"
    __table_args__ = (
        {"comment": "从influxdb发送过来的事件中提取的警告信息, 可以直接应用于业务"}
    )
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
    customer_id = Column(Integer, nullable=False, comment="品牌客户id")
    store_id = Column(Integer, nullable=False, comment="门店id")
    uuid = Column(String(64), nullable=False, comment='主机的设备UUID 用于关联和查询')
    group_id = Column(Integer, nullable=False, comment='组ID 用于查询')
    sensor_sn = Column(String(64), nullable=False, comment='传感器ID 用于查询')
    message = Column(String(256), nullable=False, comment='报警信息')
    status = Column(Integer, nullable=True, default=AlertStatus.ERR.value, comment="状态  0 恢复状态, 1 故障状态")
    value = Column(Float, nullable=True, default=None, comment='瞬态测量值 这个值会随着每一次event更新')
    # 分类有关， 2024-11-3 新增
    sensor_category = Column(String(64), nullable=True, default=None, comment='传感器分类 SensorCategory')
    type = Column(String(64), nullable=True, default=None, comment='报警类型 SensorEventType ElectricitySensorEventType')
    # 值和告警等级 2025-8-28 新增
    begin_value = Column(String(31), nullable=True, default=None, comment='报警开始时的测量值')
    end_value = Column(String(31), nullable=True, default=None, comment='报警结束时的测量值')
    priority = Column(String(31), nullable=True, default=EventPriority.WARNING.value, comment='报警优先级')
    # 以下是满足报警条件的后的信息
    alert_time = Column(DateTime, nullable=False, comment='满足报警条件的开始时间')
    update_time = Column(DateTime, nullable=True, default=None, comment='满足报警条件期间的最后更新时间')
    recovery_time = Column(DateTime, nullable=True, default=None, comment='不在满足报警条件的恢复时间')
    alert_duration = Column(Integer, nullable=True, default=None, comment='满足报警条件的持续时间 单位秒')
    # 以下是报警的配置
    conf = Column(JSON, nullable=True, default=dict, comment='报警配置 目前仅作记录，不参与业务')

    @classmethod
    def add_or_update_temperature_threshold_alert(cls, event: TemperatureSensorEventOrm, 
                                                  conf: TemperatureSensorAlertSetting, db_session: Session):
        """
        添加/更新一条温度阈值报警记录
        在调用此函数之前，需要先进行是否打开了对应的警告的判断
        注意，本函数会根据传感器设定判断当前的事件是否满足报警条件，如果满足则添加一条报警记录
        """
        # 计算status，
        min_threshold = 0 if conf.min_threshold is None else conf.min_threshold
        max_threshold = 0 if conf.max_threshold is None else conf.max_threshold
        if min_threshold <= event.value <= max_threshold:  # 正常阈值范围
            status = 0
        else:
            status = 1
        one: InFluxDBAlertOrm | None = cls.get_exist_alert(event=event, db_session=db_session)
        if one: # 有旧的对应的记录
            one.value = event.value
            if status == 0: # 需要把此记录标记为恢复
                one.status = 0
                one.recovery_time = event.time
                db_session.commit()
                one.update_alert_cache()  # 更新报警缓存
            else: # 更新一下报警记录
                one.update_time = event.time
                one.alert_duration = (event.time - one.alert_time).total_seconds()
                db_session.commit()
        else: # 没有旧的对应的记录
            if status == 0: # 无需添加报警记录
                pass
            else:  # 添加一条报警记录
                message = event.check_name
                one = cls(
                    sensor_category=SensorCategory.TEMPERATURE.value,
                    customer_id=event.customer_id,
                    store_id=event.store_id,
                    type=event.type,
                    uuid=event.uuid,
                    group_id=event.group_id,
                    sensor_sn=event.sensor_sn,
                    message=message,
                    status=status,  # 初始状态为未满足条件
                    value=event.value,
                    alert_time=event.time,
                    conf=conf.model_dump(),
                )
                db_session.add(one)
                db_session.commit()
                db_session.refresh(one)
                one.update_alert_cache()

    @classmethod
    def add_or_update_temperature_offline_alert(cls, event: TemperatureSensorEventOrm, db_session: Session, **kwargs):
        """
        添加/更新一条温度传感器的离线报警记录
        在调用此函数之前，需要先进行是否打开了对应的警告的判断
        注意，本函数会根据传感器设定判断当前的事件是否满足报警条件，如果满足则添加一条报警记录
        """
        # 判断是否已经存在相同的事件记录 这里不适用缓存的原因是报警事件是少数，不需要为了提升性能增加复杂性
        s = select(cls).where(and_(
                cls.uuid == event.uuid,
                cls.group_id == event.group_id,
                cls.sensor_sn == event.sensor_sn,
                cls.type == event.type,
                cls.status == 1,
            )).order_by(cls.alert_time.desc())
        one: InFluxDBAlertOrm | None = db_session.scalar(s) 
        if one: # 有旧的对应的记录 更新一下
            one.value = event.value
            one.update_time = event.time
            db_session.commit()
        else: # 没有旧的对应的记录
            # 添加一条报警记录  添加一条
            message = event.check_name
            one = cls(
                sensor_category=SensorCategory.TEMPERATURE.value,
                customer_id=event.customer_id,
                store_id=event.store_id,
                type=event.type,
                uuid=event.uuid,
                group_id=event.group_id,
                sensor_sn=event.sensor_sn,
                message=message,
                status=1, 
                value=event.value,
                alert_time=event.time
            )
            db_session.add(one)
            db_session.commit()
            db_session.refresh(one)
            one.update_alert_cache()
            
    def get_simple_info(self, **kwargs) -> dict:
        """
        获取告警的简单字典信息
        """
        return {
            "id": self.id,
            "uuid": self.uuid,
            "group_id": self.group_id,
            "sensor_sn": self.sensor_sn,
            "type": self.type,
            "conf": self.conf,
        }
    
    @classmethod
    def get_exist_alert(cls, event: TemperatureSensorEventOrm, db_session: Session) -> "InFluxDBAlertOrm":
        """
        获取一个已存在的 报警对象
        """
        key = generate_sensor_base_key(uuid=event.uuid, group_id=event.group_id, sensor_sn=event.sensor_sn)
        a_map = MEM_CACHE.get(key=THRESHOLD_CACHE_KEY)
        a_map = {} if a_map is None else a_map
        if key in a_map:
            one: InFluxDBAlertOrm | None = db_session.get(cls, a_map[key]['id'])
        else:
            one = None
        if one is None:
            # 看看是不是初始化的事情遗漏了对应的记录 这是一种补救措施
            s = select(cls).where(and_(
                    cls.uuid == event.uuid,
                    cls.group_id == event.group_id,
                    cls.sensor_sn == event.sensor_sn,
                    cls.type == event.type,
                    cls.status == 1,
                )).order_by(cls.alert_time.desc())
            one: InFluxDBAlertOrm | None = db_session.scalar(s)
            if one: # 找到了对应的记录
                one.update_alert_cache()
        return one
    
    @classmethod
    def init_alert_cache(cls) -> None:
        """
        初始化报警的缓存
        """
        db_session = new_db_session()
        s = select(cls).where(cls.status == 1)
        result = [x.get_simple_info() for x in db_session.scalars(s)]
        db_session.close()
        offline_map = {}
        threshold_map = {}
        # 无论任何类型的传感器，都可以分为离线事件和阈值事件, 而且字面值是一样的
        threshold_type = SensorEventType.THRESHOLD.value  
        offline_type = SensorEventType.OFFLINE.value
        for x in result:
            key = generate_sensor_base_key(uuid=x['uuid'], group_id=x['group_id'], sensor_sn=x['sensor_sn'])
            if x['type'] == threshold_type:
                threshold_map[key] = x
            elif x['type'] == offline_type:
                offline_map[key] = x
            else:
                logger.warning(f"发现 {x['type']} 类型的 {cls.__name__} 未被处理")
        MEM_CACHE.set(key=THRESHOLD_CACHE_KEY, value=threshold_map, timeout=0)  # 暂时用不到，因为这类事件依靠阈值报警事件恢复
        MEM_CACHE.set(key=OFFLINE_CACHE_KEY, value=offline_map, timeout=0)

    def update_alert_cache(self):
        """
        更新离线缓存和阈值缓存
        """
        key = generate_sensor_base_key(uuid=self.uuid, group_id=self.group_id, sensor_sn=self.sensor_sn)
        info = self.get_simple_info()
        # 无论任何类型的传感器，都可以分为离线事件和阈值事件, 而且字面值是一样的
        threshold_type = SensorEventType.THRESHOLD.value  
        offline_type = SensorEventType.OFFLINE.value
        if self.type == threshold_type:
            a_map = MEM_CACHE.get(THRESHOLD_CACHE_KEY)
            a_map = {} if a_map is None else a_map
            if self.status == 1:
                if key in a_map:
                    pass
                else:
                    a_map[key] = info
            else:
                if key in a_map:
                    a_map.pop(key)
                else:
                    pass
            MEM_CACHE.set(key=THRESHOLD_CACHE_KEY, value=a_map, timeout=0)
        elif self.type == offline_type:
            a_map = MEM_CACHE.get(OFFLINE_CACHE_KEY)
            a_map = {} if a_map is None else a_map
            if self.status == 1:
                if key in a_map:
                    pass
                else:
                    a_map[key] = info
            else:
                if key in a_map:
                    a_map.pop(key)
                else:
                    pass
            MEM_CACHE.set(key=OFFLINE_CACHE_KEY, value=a_map, timeout=0)
        else:
            logger.warning(f"发现 {self.type} 类型的 {self.__class__.__name__} 未被处理")

    @classmethod
    def recovery_offline_alert(cls, uuid: str, group_id: int, nodes: List[dict]):
        """
        标记离线警告为恢复
        """
        a_map = MEM_CACHE.get(key=OFFLINE_CACHE_KEY)
        a_map = {} if a_map is None else a_map
        sensor_sns = []
        for node in nodes:
            sensor_sn = node['id']
            key = generate_sensor_base_key(uuid=uuid, group_id=group_id, sensor_sn=sensor_sn)              
            if key not in a_map:
                pass
            else:             
                sensor_sns.append(sensor_sn)
                a_map.pop(key)
        if len(sensor_sns) > 0:
            db_session = new_db_session()
            now = datetime.now()
            # 必须使用属性修改的方法，因为 直接使用 update 不能触发监听事件
            for x in db_session.scalars(select(cls).where(
                cls.uuid == uuid, 
                cls.group_id == group_id,
                cls.status == 1,
                cls.sensor_sn.in_(sensor_sns)
            )):
                x.recovery_time = now
                x.status = 0
            db_session.commit()
            db_session.close()
            # 最后更新缓存
            MEM_CACHE.set(key=OFFLINE_CACHE_KEY, value=a_map, timeout=0)

    @classmethod
    def simple_repair_priority(cls):
        """
        简单的修复优先级, 只是为了测试的时候有数据，一次性函数
        """
        db_session = new_db_session()
        s = select(cls).where(cls.alert_time  > datetime.now() - timedelta(days=3))
        num = 0
        for x in db_session.scalars(s):
            if x.type == '设备离线':
                x.priority = EventPriority.ALERT.value
            else:
                x.priority = EventPriority.WARNING.value
            num += 1
        db_session.commit()
        db_session.close()
        logger.debug(f"{num} 条记录被更新")
        return 


def sensor_alert_listener(class_: type, key: str, inst: InstrumentedAttribute):
    """
    传感器警告监听器， 这个函数会在 db_session.commit() 之前被调用
    :param class_: Orm 类
    :param key: 属性名
    :param inst: 属性对应的监听属性
    :return: _description_
    """
    # 定义一个监听函数
    def set_(instance: InFluxDBAlertOrm, new_value: Any, old_value: Any, initiator: AttributeEventToken, **kwargs):  
        # 如果是新创建的 instance 那么 old_value 的值是  sqlalchemy.orm.LoaderCallableStatus.NO_VALUE 
        if new_value  ==  old_value:
            pass
        else:
            logger.debug(f"{instance.__class__.__name__}对象(ID={instance.id})的{key}属性值变化: {old_value} -> {new_value}")
            db_session = new_db_session()
            s = select(SensorOrm).where(
                SensorOrm.uuid == instance.uuid, 
                SensorOrm.group_id == instance.group_id, 
                SensorOrm.sensor_sn == instance.sensor_sn
            )
            one: SensorOrm = db_session.scalars(s).one_or_none()
            if one is None:
                logger.warning(f"未找到 {instance.uuid} {instance.group_id} {instance.sensor_sn} 对应的传感器")
            else:
                kw = {
                        "old_status": old_value,
                        "new_status": new_value,
                        "type": instance.type,
                    }

                if instance.type == SensorEventType.THRESHOLD.value: # 如果是超过阈值                  
                    logger.warning(f"传感器 {instance.uuid} {instance.group_id} {instance.sensor_sn} 阈值状态{old_value} -> {new_value}")
                else:   # 如果是离线
                    logger.warning(f"传感器 {instance.uuid} {instance.group_id} {instance.sensor_sn} 离线状态{old_value} -> {new_value}")
                # 更新传感器状态
                one.update_status_and_send_message(**kw)  # 更新传感器状态                    
                db_session.commit()
                logger.warning(f"传感器 {instance.uuid} {instance.group_id} {instance.sensor_sn} {instance.type}更新为 {new_value}")
                db_session.close()
   
    if key == 'status':
        event.listen(inst, "set", set_)  # 监听 status 字段的 set 事件，回调函数为 set_
    

event.listen(InFluxDBAlertOrm, 'attribute_instrument', sensor_alert_listener)  # 注册监听器必须在 create_all 之前，否则不会生效



################电量传感器###########################


class AddElectricitySensorParams(BaseModel):
    """
    添加电量传感器参数
    """
    sensor_category: str = Field(default=SensorCategory.ELECTRICITY.value, description="传感器类型")
    merchant_no: str | None = Field(None, description="商户iD 仅限第三方平台")
    uuid: str = Field(..., description="主机的设备UUID 用于关联和查询")
    group_id: int = Field(..., description="组ID 用于查询")
    sensor_sn: str = Field(..., description="传感器ID 用于查询")
    is_main: int = Field(..., description="是否为主传感器 0-否 1-是")
    sensor_name: str | None = Field(None, description="传感器名称 冗余字段")
    mode_type: str | None = Field(None, description="传感器型号 冗余字段")
    manufacturer: str = Field(..., description="传感器厂商 必须 适用对应的接口获取选项")
    alias: str = Field(..., description="传感器别名 必须")
    desc: str | None = Field(None, description="传感器描述")
    picture_id: int | None = Field(None, description="图片id")
    customer_id: int = Field(..., description="品牌用户id")
    customer_name: str = Field(..., description="品牌用户名称")
    store_id: int = Field(..., description="门店id")
    store_name: str = Field(..., description="门店名称")
    threshold_alert: int | None = Field(1, description="是否开启阈值报警 0-不开启 1-开启")
    offline_alert: int | None = Field(1, description="是否开启离线报警 0-不开启 1-开启")
    # 电量传感器专有属性
    over_voltage: int | None = Field(242, description="过压阈值")
    under_voltage: int | None = Field(198, description="欠压阈值")
    over_current: int | None = Field(None, description="过流阈值")
    under_current: int | None = Field(None, description="欠流阈值")
    leakage_current: int | None = Field(None, description="漏电流阈值")
    over_power: int | None = Field(None, description="过功率阈值")
    height_temperature: int | None = Field(40, description="高温阈值")


class EditElectricitySensorParams(BaseModel):
    """
    编辑电量传感器参数
    """
    id: int = Field(..., description="传感器ID")
    merchant_no: str | None = Field(None, description="商户iD 仅限第三方平台")
    operator_customer_id: int | None = Field(None, description="操作用户id")
    uuid: str | None = Field(None, description="主机的设备UUID 用于关联和查询")
    group_id: int | None = Field(None, description="组ID 用于查询")
    sensor_sn: str | None = Field(None, description="传感器ID 用于查询")
    is_main: int | None = Field(None, description="是否为主传感器 0-否 1-是")
    sensor_name: str | None = Field(None, description="传感器名称 冗余字段")
    mode_type: str | None = Field(None, description="传感器型号 冗余字段")
    manufacturer: str | None = Field(None, description="传感器厂商 必须 适用对应的接口获取选项")
    alias: str | None = Field(None, description="传感器别名 必须")
    desc: str | None = Field(None, description="传感器描述")
    picture_id: int | None = Field(None, description="图片id")
    customer_id: int | None = Field(None, description="品牌用户id")
    customer_name: str | None = Field(None, description="品牌用户名称")
    store_id: int | None = Field(None, description="门店id")
    store_name: str | None = Field(None, description="门店名称")
    threshold_alert: int | None = Field(None, description="是否开启阈值报警 0-不开启 1-开启")
    offline_alert: int | None = Field(None, description="是否开启离线报警 0-不开启 1-开启")
    # 电量传感器专有属性
    over_voltage: int | None = Field(None, description="过压阈值")
    under_voltage: int | None = Field(None, description="欠压阈值")
    over_current: int | None = Field(None, description="过流阈值")
    under_current: int | None = Field(None, description="欠流阈值")
    leakage_current: int | None = Field(None, description="漏电流阈值")
    over_power: int | None = Field(None, description="过功率阈值")
    height_temperature: int | None = Field(None, description="高温阈值")


class ElectricitySensorAlertSetting(AlertSetting):
    """
    电量传感器报警设置
    """
    over_voltage: int | None = Field(None, description="过压阈值")
    under_voltage: int | None = Field(None, description="欠压阈值")
    over_current: int | None = Field(None, description="过流阈值")
    under_current: int | None = Field(None, description="欠流阈值")
    leakage_current: int | None = Field(None, description="漏电流阈值")
    over_power: int | None = Field(None, description="过载阈值")
    height_temperature: int | None = Field(None, description="高温阈值")


class ElectricitySensorCategoryOrm(Base, OrmTools):
    """
    电量传感器分类
    """
    __tablename__ = 'electricity_sensor_category'
    __table_args__ = (
        {'comment': '电量传感器分类'}
    )
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False, comment="分类名称")
    desc = Column(String(255), nullable=True, comment="分类描述")

    @classmethod
    def get_selector(cls) -> List[dict]:
        db_session = new_db_session()
        s = select(cls).order_by(cls.id)
        result = [{"name": x.name, "id": x.id} for x in  db_session.scalars(s)]
        db_session.close()
        return result

    
class ElectricitySensorOrm(SensorOrm):
    """
    电力传感器
    目前并不完善，没有展开使用 2025-2-7
    """
    __tablename__ = 'electricity_sensor'
    __table_args__ = (
        {'comment': '电量传感器'}
    )
    id = Column(ForeignKey("sensor.id", ondelete='DELETE'), primary_key=True)
    category_id = Column(ForeignKey("electricity_sensor_category.id", ondelete='SET NULL'), nullable=True, comment="分类id")
    category = relationship("ElectricitySensorCategoryOrm", backref="electricity_sensors", doc="分类")
    # 天勍专用数据
    # 设置
    is_main = Column(Integer, nullable=True, default=0, comment="是否主传感器 0/不是，1/是， 2/虚拟主传感器")
    over_voltage = Column(Float, nullable=True, default=242,  comment='过压')
    under_voltage = Column(Float, nullable=True, default=198, comment='欠压')
    over_current = Column(Float, nullable=True, default=None, comment='过流')
    under_current = Column(Float, nullable=True, default=None, comment='欠流')
    leakage_current = Column(Float, nullable=True, default=None, comment='漏电流')
    over_power = Column(Float, nullable=True, default=None, comment='过功率')
    height_temperature = Column(Float, nullable=True, default=40, comment='高温')

    __mapper_args__ = {
        'polymorphic_identity': SensorCategory.ELECTRICITY.value,
    }

    @classmethod
    def get_main_sensor_id(cls, store_id: int, db_session: Session) -> int:
        """
        获取主电量传感器id
        :param db_session: _description_
        :return: _description_
        """
        s = select(cls.id).where(
            cls.store_id == store_id,
            cls.is_main == 1
        )
        s = db_session.scalar(s)
        return s
    
    @classmethod
    def add(cls, params: AddElectricitySensorParams) -> Resp:
        db_session, resp = new_db_session(), Resp()
        s = select(SensorOrm).where(and_(
            SensorOrm.uuid == params.uuid,
            SensorOrm.group_id == params.group_id,
            SensorOrm.sensor_sn == params.sensor_sn,
        ))
        one = db_session.scalar(s)
        if one:
            resp.message = '设备已存在'
        else:
            kw = params.model_dump()
            obj = cls(**kw)
            db_session.add(obj)
            db_session.commit()
            db_session.refresh(obj)
            reload_sensor_conf_cache(obj)  # 重新加载配置缓存
            resp.data = {
                "store_id": obj.store_id,
                "current_sensor_id": obj.id,
                "sensor_category": obj.sensor_category,
                "main_electricity_sensor_id": obj.get_main_sensor_id(obj.store_id, db_session),  # 主传感器id
                "sensor_ids": cls.get_store_sensor_ids(customer_id=params.customer_id,
                                                       store_id=params.store_id,
                                                       sensor_category=obj.sensor_category,
                                                       db_session=db_session)
            }
        db_session.close()
        return resp
    
    @classmethod
    def edit(cls, params: EditElectricitySensorParams) -> Resp:
        db_session, resp = new_db_session(), Resp()
        one = db_session.get(cls, params.id)
        if not one:
            resp.message = f"传感器不存在: ID = {params.id}"
        elif params.operator_customer_id and one.customer_id != params.operator_customer_id:
            resp.message = "不能操作其他公司的传感器"
        elif db_session.scalar(select(func.count(cls.id)).where(and_(
            cls.uuid == params.uuid,
            cls.group_id == params.group_id,
            cls.sensor_sn == params.sensor_sn,
            cls.id != params.id
        ))) > 0:
            resp.message = '设备已存在'
        else:
            kw = params.model_dump(exclude=['id', 'operator_customer_id'], exclude_none=True)
            for k, v in kw.items():
                if hasattr(one, k):
                    setattr(one, k, v)
            db_session.commit()
            db_session.refresh(one)
            reload_sensor_conf_cache(one)  # 重新加载配置缓存
            resp.data = {
                "store_id": one.store_id,
                "current_sensor_id": one.id,
                "sensor_category": one.sensor_category,
                "main_electricity_sensor_id": one.get_main_sensor_id(one.store_id, db_session),  # 主传感器id
                "sensor_ids": cls.get_store_sensor_ids(customer_id=one.customer_id,
                                                       sensor_category=one.sensor_category,
                                                            store_id=one.store_id,
                                                            db_session=db_session)
            }
        db_session.close()
        return resp
    
    @classmethod
    def delete(cls, ids: List[int]) -> Resp:
        db_session, resp = new_db_session(), Resp()
        if not ids:
            resp.message = "缺少参数"
        else:
            customer_id, sensor_category, store_id = None, None, None
            picture_ids = set()
            for id in ids:
                sensor = db_session.get(cls, id)
                if sensor is None:
                    resp.message = f"无效的传感器id: {id}"
                    break
                if sensor.picture_id:
                    picture_ids.add(sensor.picture_id)
                if customer_id is None:
                    customer_id = sensor.customer_id
                    sensor_category = sensor.sensor_category
                    store_id = sensor.store_id
                else:
                    if store_id != sensor.store_id:
                        resp.message = "只能删除同一个门店的传感器"
                        break
                remove_sensor_conf_cache(sensor)  # 移除配置缓存
            if resp:
                s = delete(cls).where(cls.id.in_(ids))
                s0 = delete(SensorOrm).where(SensorOrm.id.in_(ids))
                result = db_session.execute(s)
                db_session.execute(s0)
                db_session.commit()
                logger.warning(f"删除结果: {result}")
                resp.data = {
                    "store_id": store_id,
                    "current_sensor_id": ids,
                    "sensor_category": sensor_category,
                    "picture_ids": list(picture_ids), # 需要删除的图片id
                    "main_electricity_sensor_id": cls.get_main_sensor_id(store_id, db_session),  # 主传感器id
                    "sensor_ids": cls.get_store_sensor_ids(customer_id=customer_id,
                                                           sensor_category=sensor_category,
                                                                store_id=store_id,
                                                                db_session=db_session)
                }
            else:
                pass      
        db_session.close()
        return resp
    
    def get_alert_setting(self) -> ElectricitySensorAlertSetting:
        return ElectricitySensorAlertSetting(sensor_category=self.sensor_category,
                                             threshold_alert=self.threshold_alert,
                                             offline_alert=self.offline_alert,
                                             over_voltage=self.over_voltage,
                                             under_voltage=self.under_voltage,
                                             over_current=self.over_current,
                                             under_current=self.under_current,
                                             leakage_current=self.leakage_current,
                                             over_power=self.over_power,
                                             height_temperature=self.height_temperature,
                                             )
    

class ThirdPartySensorManager(BaseModel):
    """
    第三方传感器管理器
    """
    cache_key: str = Field(default=THRESHOLD_CACHE_KEY, description="缓存key")


class AddMessagePushRecordParams(BaseModel):
    """
    消息推送记录
    """
    id: int | None = Field(default=None, description="主键id")
    customer_id: int = Field(..., description="品牌客户id")
    customer_name: str = Field(..., description="品牌客户名称")
    store_id: int = Field(..., description="门店id")
    store_name: str = Field(..., description="门店名称")
    sensor_id: int = Field(..., description="传感器id")
    sensor_name: str = Field(..., description="传感器名称")
    prev_status: int = Field(..., description="传感器前状态")
    sensor_status: int = Field(..., description="传感器现状态")
    type: str = Field(..., description="传感器事件类型  SensorEventType")
    sensor_category: str = Field(..., description="传感器类型")
    message: str = Field(..., description="消息内容")
    alert_time: datetime = Field(..., description="报警时间")
    raw_obj_id: int = Field(..., description="原始对象id")

    def save_orm(self) -> Resp:
        """
        保存到数据库
        :return: _description_
        """
        resp = MessagePushRecordOrm.add(self)
        if resp:
            self.id = resp.data  # 补充id，
        return resp

    def execute_task(self) -> Resp:
        """
        执行任务
        :return: _description_
        """
        # 先保存
        resp = self.save_orm()
        if not resp:
            logger.error(f"保存推送任务失败: error: {resp.message}")
            return resp
        logger.debug(f"保存推送任务成功")
        # 再发送到任务中心
        resp = self.send_to_task_center()
        return resp
    
    def send_to_task_center(self):
        """
        发送消息到任务中心
        """     
        kw = self.model_dump(exclude=['id'])
        kw = {k: v.strftime("%Y-%m-%d %H:%M:%S") if isinstance(v, datetime) else v for k,v in kw.items()}
        push_time = datetime.now()
        kw['push_time'] = push_time.strftime("%Y-%m-%d %H:%M:%S")
        # 发送消息到任务中心
        url = f"{TASK_URL}/v1/event/sensor"
        headers = {
            "Authorization": TASK_AUTHORIZATION,
        }
        error = None
        logger.debug(f"发送消息到任务中心: url={url}; kw = {kw}")
        try:         
            response = requests.post(url, json=kw, headers=headers, timeout=10)
            if response.status_code != 200:
                error = f"发送消息到任务中心失败: {response.text}"
            else:
                logger.info(f"发送消息到任务中心成功: {response.text}")
        except Exception as e:
            error = f"发送消息到任务中心失败: {e}"
        finally:
            if error:
                logger.error(error)
                push_result = error[0:128]
            else:
                push_result = "success"
            MessagePushRecordOrm.update_push_result(id=self.id, push_result=push_result, push_time=push_time)


class MessagePushRecordOrm(Base, OrmTools):
    """
    消息推送记录
    """
    __tablename__ = "message_push_record"
    __table_args__ = {
        "comment": "消息推送记录"
    }
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键id")
    customer_id = Column(Integer, comment="客户id")
    customer_name = Column(String(255), nullable=False, comment="客户名称")
    store_id = Column(Integer, nullable=False, comment="门店id")
    store_name = Column(String(64), nullable=False, comment="门店名称")
    sensor_id = Column(Integer, nullable=False, comment="传感器id")
    sensor_name = Column(String(128), nullable=False, comment="传感器名称")
    prev_status = Column(Integer, nullable=False, comment="传感器前状态 可能是离线状态也可能是阈值状态 要结合事件类型做判定")
    sensor_status = Column(Integer, nullable=False, comment="传感器状态 可能是离线状态也可能是阈值状态 要结合事件类型做判定")
    type = Column(String(64), nullable=False,  comment="传感器事件类型 SensorEventType")
    sensor_category = Column(String(64), nullable=False, comment="传感器类型")
    message = Column(String(256), nullable=False, comment="消息内容")
    alert_time = Column(DateTime, nullable=False, comment="报警时间")
    push_time = Column(DateTime, nullable=True, default=None, comment="推送时间")
    raw_obj_id = Column(String(128), nullable=True, default=None, comment="原始对象id")
    push_time = Column(DateTime, nullable=True, default=None, comment="推送时间")
    push_result = Column(String(128), nullable=True, default=None, comment="推送结果")
    create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
    update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")

    @classmethod
    def add(cls, params: AddMessagePushRecordParams) -> Resp:
        """
        添加消息推送记录
        :param kwargs:
        :return:
        """
        db_session, resp = new_db_session(), Resp()
        if params.id:
            resp.data = params.id  # 这是从数据库里拉出来的上次未完成的任务
        else:
            one = cls(**params.model_dump())
            db_session.add(one)
            db_session.commit()
            db_session.refresh(one)
            resp.data = one.id
        db_session.close()
        return resp
    
    @classmethod
    def update_push_result(cls, id: int, push_result: str, push_time: datetime) -> Resp:
        """
        更新推送结果
        :param id:
        :param push_result:
        :return:
        """
        db_session, resp = new_db_session(), Resp()
        one = db_session.query(cls).filter(cls.id == id).first()
        if one:
            one.push_result = push_result
            one.push_time = push_time
            db_session.commit()
        else:
            resp.message = f"消息推送记录不存在 id = {id}"
            logger.error(resp.message)
        db_session.close()
        return resp
    
    def get_task_instance(self) -> AddMessagePushRecordParams:
        """
        获取任务实例
        :return:
        """
        ignore = [
            "create_time", "update_time", "push_result", "push_time"
        ]
        return AddMessagePushRecordParams(**self.to_dict(ignore=ignore))

    @classmethod
    def resend(cls, id: int) -> Resp:
        """
        重新发送消息
        :param id:
        :return:
        """
        db_session, resp = new_db_session(), Resp()
        one = db_session.get(cls, id)
        db_session.close()
        if one:
            task = one.get_task_instance()
            task.send_to_task_center()
        else:
            resp.message = f"消息推送记录不存在 id = {id}"
            logger.error(resp.message)

            
if __name__ == '__main__':
    
    pass